import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer


# lin model
def model(x, theta):
    return x.dot(theta)


# sigmoid
def sigmoid(z):
    return 1 / (1 + np.exp(-z))


# cost function
def cost(h, y):
    """

    :param h: matrix
    :param y: matrix
    :return: scalar
    """
    m = len(y)
    return -1/m * np.sum(y*np.log(h) + (1-y)*np.log(1-h))


# forward propagation
def FP(x, theta1, theta2, theta3):
    a1 = x
    z2 = model(a1, theta1)
    a2 = sigmoid(z2)
    z3 = model(a2, theta2)
    a3 = sigmoid(z3)
    z4 = model(a3, theta3)
    a4 = sigmoid(z4)
    return a2, a3, a4


# backward progapation
def BP(x, y, theta1, theta2, theta3, a2, a3, a4, alpha):
    m, n = x.shape
    s4 = a4 - y
    s3 = s4.dot(theta3.T) * (a3 * (1 - a3))
    s2 = s3.dot(theta2.T) * (a2 * (1 - a2))

    dt3 = 1/m * a3.T.dot(s4)
    dt2 = 1/m * a2.T.dot(s3)
    dt1 = 1/m * x.T.dot(s2)

    theta3 -= alpha * dt3
    theta2 -= alpha * dt2
    theta1 -= alpha * dt1
    return theta1, theta2, theta3


# gradient descent
def grad(x, y, alpha=0.01, iter0=15000):
    L2, L3, L4 = 50, 100, 1
    m, n = x.shape
    theta1 = np.random.randn(n, L2)
    theta2 = np.random.randn(L2, L3)
    theta3 = np.random.randn(L3, L4)
    J = np.zeros(iter0)
    for i in range(iter0):
        a2, a3, a4 = FP(x, theta1, theta2, theta3)
        J[i] = cost(a4, y)
        theta1, theta2, theta3 = BP(x, y, theta1, theta2, theta3, a2, a3, a4, alpha)
    return theta1, theta2, theta3, J, a4


# accuracy
def score(h, y):
    return np.mean(y == (h > 0.5))


# load data, train model
if '__main__' == __name__:
    np.random.seed(1)
    cancer = load_breast_cancer()
    x = cancer.data
    y = cancer.target.reshape(-1, 1)

    # scale data
    mu = np.mean(x, axis=0)  # ATTENTION axis=0
    sigma = np.std(x, axis=0)  # ATTENTION axis=0
    x -= mu
    x /= sigma

    X = np.c_[np.ones(len(x)), x]

    iter0 = 2000
    alpha = 0.1
    theta1, theta2, theta3, J, a4 = grad(X, y, alpha, iter0)

    print(f'Score: {score(a4, y)}')

    plt.plot(J)
    plt.show()
